#include "D3DUtils.h"

bool D3DUtil::IsKeyDown(int InKeyCode)
{
    return (GetAsyncKeyState(InKeyCode) & 0x8000) != 0;
}

Microsoft::WRL::ComPtr<ID3D10Blob> D3DUtil::LoadBinary(const std::wstring& InFileName)
{
    std::ifstream FIn(InFileName, std::ios::binary);

    FIn.seekg(0, std::ios_base::end);
    std::ifstream::pos_type FileSize = (int)FIn.tellg();
    FIn.seekg(0, std::ios_base::beg);

    Microsoft::WRL::ComPtr<ID3D10Blob> ResBlob;
    ThrowIfFailed(D3DCreateBlob(FileSize, ResBlob.GetAddressOf()));

    FIn.read((char*)ResBlob->GetBufferPointer(), FileSize);
    FIn.close();

    return ResBlob;
}

Microsoft::WRL::ComPtr<ID3DBlob> D3DUtil::CompileShader(
    const std::wstring& InFileName,
    const D3D_SHADER_MACRO* InDefines,
    const std::string& InEntryPoint,
    const std::string& InTarget)
{
    UINT CompileFlags = 0;
#if defined(DEBUG) || defined(_DEBUG)
    CompileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#endif

    HRESULT CompileShaderResult = S_OK;
    
    Microsoft::WRL::ComPtr<ID3DBlob> ByteCode = nullptr;
    Microsoft::WRL::ComPtr<ID3DBlob> ErrorBlob;

    CompileShaderResult = D3DCompileFromFile(InFileName.c_str(), InDefines, D3D_COMPILE_STANDARD_FILE_INCLUDE,
        InEntryPoint.c_str(), InTarget.c_str(), CompileFlags, 0, &ByteCode, &ErrorBlob);

    if (ErrorBlob != nullptr)
        OutputDebugStringA((char*)ErrorBlob->GetBufferPointer());

    ThrowIfFailed(CompileShaderResult);

    return ByteCode;
}

Microsoft::WRL::ComPtr<ID3D12Resource> D3DUtil::CreateDefaultBuffer(
        ID3D12Device* InDevice,
        ID3D12GraphicsCommandList* InCmdList,
        const void* InData,
        UINT64 InByteSize,
        Microsoft::WRL::ComPtr<ID3D12Resource>& OutUploadBuffer)
{
    Microsoft::WRL::ComPtr<ID3D12Resource> DefaultBuffer;

    // 创建实际的默认缓冲区资源
    D3D12_HEAP_PROPERTIES DefaultHeapProps(D3D12_HEAP_TYPE_DEFAULT);
    D3D12_RESOURCE_DESC DefaultDesc = CD3DX12_RESOURCE_DESC::Buffer(InByteSize);
    ThrowIfFailed(InDevice->CreateCommittedResource(
        &DefaultHeapProps,
        D3D12_HEAP_FLAG_NONE,
        &DefaultDesc,
        D3D12_RESOURCE_STATE_COMMON,
        nullptr,
        IID_PPV_ARGS(DefaultBuffer.GetAddressOf())));

    // 为了将CPU端内存中的数据赋值到默认缓冲区，我们还需要创建一个处于中介位置的上传堆
    D3D12_HEAP_PROPERTIES UploadHeapProps(D3D12_HEAP_TYPE_UPLOAD);
    D3D12_RESOURCE_DESC UploadDesc = CD3DX12_RESOURCE_DESC::Buffer(InByteSize);
    ThrowIfFailed(InDevice->CreateCommittedResource(
        &UploadHeapProps,
        D3D12_HEAP_FLAG_NONE,
        &UploadDesc,
        D3D12_RESOURCE_STATE_GENERIC_READ,
        nullptr,
        IID_PPV_ARGS(OutUploadBuffer.GetAddressOf())));

    // 描述我们希望复制到默认缓冲区中的数据
    D3D12_SUBRESOURCE_DATA SubResourceData = {};
    SubResourceData.pData = InData;
    SubResourceData.RowPitch = InByteSize;
    SubResourceData.SlicePitch = SubResourceData.RowPitch;

    // 将数据赋值到默认缓冲区资源的流程
    // UpdateSubresources辅助函数会先将数据从CPU端的内存中复制到位于中介位置的上传堆里，接着
    // 再通过调用ID3D12CommandList::CopySubresourceRegion函数，把上传堆内的数据赋值到
    // DefaultBuffer中
    D3D12_RESOURCE_BARRIER MapResourceBarrier = CD3DX12_RESOURCE_BARRIER::Transition(DefaultBuffer.Get(),
            D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST);
    InCmdList->ResourceBarrier(1, &MapResourceBarrier);
    
    UpdateSubresources<1>(InCmdList, DefaultBuffer.Get(), OutUploadBuffer.Get(),
        0, 0, 1, &SubResourceData);

    D3D12_RESOURCE_BARRIER UnmapResourceBarrier = CD3DX12_RESOURCE_BARRIER::Transition(DefaultBuffer.Get(),
            D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_GENERIC_READ);
    InCmdList->ResourceBarrier(1, &UnmapResourceBarrier);

    // 注意：在调用上述函数后，必须保证UploadBuffer依然存在，而不能对它立即进行销毁。这是因为
    // 命令列表中的复制操作可能尚未执行。待调用者得知复制完成的消息后，方可释放UploadBuffer
    return DefaultBuffer;
}

DxException::DxException(HRESULT hr, const std::wstring& functionName, const std::wstring& filename, int lineNumber) :
    ErrorCode(hr),
    FunctionName(functionName),
    Filename(filename),
    LineNumber(lineNumber)
{
}

std::wstring DxException::ToString()const
{
    // Get the string description of the error code.
    _com_error err(ErrorCode);
    std::wstring msg = err.ErrorMessage();

    return FunctionName + L" failed in " + Filename + L"; line " + std::to_wstring(LineNumber) + L"; error: " + msg;
}